package awesome.cpu.gui;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.border.Border;
import javax.swing.filechooser.FileFilter;

import awesome.cpu.control.CentralProcessorUnit;
import awesome.cpu.control.Clock;
import awesome.cpu.structure.BusMonitor;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Map;
import java.util.Map.Entry;

public class MainWindow extends JFrame {
	private static final long serialVersionUID = 860808473915963693L;
	
	private final static int MARGIN_LEFT = 60;
	private final static int MARGIN_RIGHT = 60;
	private final static int MARGIN_TOP = 50;
	private final static int MARGIN_BOTTOM = 50;
	
	private final static MainWindow mInstance = new MainWindow();
	
	ControlPanel mControl;
	MainPanel mMain;
	
	public static MainWindow getInstance() {
		return mInstance;
	}

	private MainWindow() {
		super();

		this.setTitle("Awesome CPU Emulator");
		
		JPanel content = new JPanel();
		this.setContentPane(content);
		
		content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
		mControl = new ControlPanel();
		content.add(mControl);
		content.add(new GraphReferencePanel());
		mMain = new MainPanel();
		content.add(mMain);
		
		this.setLocation(MARGIN_LEFT, MARGIN_TOP);
		Dimension d = getWindowDimension();
		this.setPreferredSize(d);
		this.setMaximumSize(d);
		this.setMinimumSize(d);
		this.setResizable(false);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
	
	private Dimension getWindowDimension() {
		Dimension scr = Toolkit.getDefaultToolkit().getScreenSize();
		return new Dimension(scr.width - MARGIN_LEFT - MARGIN_RIGHT,
				scr.height - MARGIN_TOP - MARGIN_BOTTOM);
	}
	
	private class ControlPanel extends JPanel {
		private static final long serialVersionUID = 5317925632291116669L;
		
		ControlPanel() {
			super();
			this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
			
			JPanel upper = new JPanel();
			upper.setLayout(new BoxLayout(upper, BoxLayout.X_AXIS));
			JLabel label = new JLabel("Select a clock cycle length:");
			upper.add(label);
			final JComboBox combo = new JComboBox(new String[] {
				"500ms", "1000ms", "2000ms"
			});
			combo.setMaximumSize(combo.getPreferredSize());
			upper.add(combo);
			final JCheckBox verbose = new JCheckBox("Output details");
			upper.add(verbose);
			java.awt.Component glue = Box.createGlue();
			glue.setMaximumSize(new Dimension(200, glue.getPreferredSize().height));
			upper.add(glue);
			JButton choose = new JButton("Choose File...");
			upper.add(choose);
			choose.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent arg0) {
					JFileChooser fc = new JFileChooser();
					fc.setDialogTitle("Open Source File");
					fc.setFileFilter(new FileFilter() {

						@Override
						public boolean accept(File file) {
							return file.isDirectory() || 
									file.getName().toLowerCase().endsWith(".asm");
						}

						@Override
						public String getDescription() {
							return "Assembly Source File";
						}
					});
					fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
					int result = fc.showOpenDialog(MainWindow.this);
					if (result == JFileChooser.APPROVE_OPTION) {
						File file = fc.getSelectedFile();
						mMain.setSrcCode(file);
					}
				}
			});
			JButton run = new JButton("Run");
			run.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent arg0) {
					int cycleIndex = combo.getSelectedIndex();
					switch (cycleIndex) {
					case 0:
						Clock.getInstance().setClockCycle(500);
						mMain.graph.setAnimationTime(300);
						break;
					case 1:
						Clock.getInstance().setClockCycle(1000);
						mMain.graph.setAnimationTime(700);
						break;
					case 2:
						Clock.getInstance().setClockCycle(2000);
						mMain.graph.setAnimationTime(1500);
						break;
					}
					BusMonitor.getInstance().setVerbose(verbose.isSelected());
					mMain.output.setText("Output:\n");
					CentralProcessorUnit.getInstance().start();
					mMain.graph.startAnimation();
				}
			});
			upper.add(run);
			JButton stop = new JButton("Stop");
			stop.addActionListener(new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					CentralProcessorUnit.getInstance().makeStop();
					mMain.graph.updateUI();
				}
			});
			upper.add(stop);
			glue = Box.createGlue();
			glue.setMaximumSize(new Dimension(200, glue.getPreferredSize().height));
			upper.add(glue);
			JButton exportLog = new JButton("Export Log");
			exportLog.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					JFileChooser fc = new JFileChooser();
					fc.setDialogTitle("Save Log File");
					fc.setFileFilter(new FileFilter() {
						@Override
						public boolean accept(File f) {
							return f.isDirectory() || f.getName().toLowerCase().endsWith(".log");
						}
						@Override
						public String getDescription() {
							return "Awesome CPU Emulator Log file";
						}
					});
					fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
					int result = fc.showSaveDialog(MainWindow.this);
					if (result == JFileChooser.APPROVE_OPTION) {
						File file = fc.getSelectedFile();
						mMain.saveLogFile(file);
					}
				}
			});
			upper.add(exportLog);
			this.add(upper);
		}
	}
	
	private class GraphReferencePanel extends JPanel {
		private static final long serialVersionUID = 2693059634738674261L;

		private final static int height = 100;
		
		GraphReferencePanel() {
			super();
			Dimension size = new Dimension(getWindowDimension().width, height);
			this.setPreferredSize(size);
			this.setMaximumSize(size);
			this.setMinimumSize(size);
			this.setBackground(Color.white);
			this.setBorder(new Border() {

				@Override
				public Insets getBorderInsets(Component arg0) {
					return new Insets(2, 2, 2, 2);
				}

				@Override
				public boolean isBorderOpaque() {
					return false;
				}

				@Override
				public void paintBorder(Component c, Graphics g,
						int x, int y, int width, int height) {
					Insets insets = this.getBorderInsets(c);
					g.drawRect(x+insets.left, y+insets.top, 
							width-insets.left-insets.right,
							height-insets.top-insets.bottom);
				}
			});
			
			this.setLayout(new GridLayout(4, 6));
			Map<String, Integer> map = OutputGraph.getNameColorMap();
			for (final Entry<String, Integer> entry : map.entrySet()) {
				JPanel p = new JPanel() {
					private static final long serialVersionUID = -6317922328892303017L;

					@Override
					public void paint(Graphics g) {
						g.setColor(new Color(entry.getValue()));
						g.fillRect(5, 5, 10, 10);
						g.setColor(Color.black);
						g.drawString(entry.getKey(), 20, 3+g.getFont().getSize());
					}
				};
				p.setPreferredSize(new Dimension(30, 10));
				this.add(p);
			}
		}
	}
	
	private class MainPanel extends JPanel {
		private static final long serialVersionUID = -5883594974034471687L;
		
		OutputGraph graph;
		JTextArea srcCode;
		JTextArea output;
		
		MainPanel() {
			super();			
			this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));

			graph = new OutputGraph();
			this.add(graph);
			
			Dimension d = new Dimension
				((getWindowDimension().width-graph.getPreferredSize().width)/2,
				graph.getPreferredSize().height);
			
			srcCode = new JTextArea("Input:\n");
			JScrollPane s1 = new JScrollPane(srcCode);
			s1.setPreferredSize(d);
			s1.setMaximumSize(d);
			s1.setMinimumSize(d);
			this.add(s1);
			output = new JTextArea("Output:\n");
			output.setEditable(false);
			JScrollPane s2 = new JScrollPane(output);
			s2.setPreferredSize(d);
			s2.setMaximumSize(d);
			s2.setMinimumSize(d);
			this.add(s2);
		}
		
		private void setSrcCode(File file) {
			try {
				StringBuffer buf = new StringBuffer();
				FileInputStream fis = new FileInputStream(file);
				InputStreamReader isr = new InputStreamReader(fis);
				BufferedReader br = new BufferedReader(isr);
				String line;
				while ((line = br.readLine()) != null) {
					if (!line.matches(" *")) {
						buf.append(line);
						buf.append('\n');
					}
				}
				srcCode.setText(buf.toString());
				fis.close();
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	
		private void saveLogFile(File file) {
			if (!file.exists()) {
				try {
					file.createNewFile();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
			try {
				FileOutputStream fos = new FileOutputStream(file);
				BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
				bw.write(output.getText());
				bw.flush();
				bw.close();
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public String getInputSrcCode() {
		return mMain.srcCode.getText();
	}
	
	void appendOutput(String output) {
		mMain.output.append(output);
	}
}